
import pyglet

def enumerate_joysticks(bPrint=False):
    joysticks = pyglet.input.get_joysticks()

    for joystick in joysticks:
        # name unnamed controls in the joystick
        btn_cnt = 0
        ot_cnt = 0
        ctrl_cnt = 0
        if bPrint:
            print '\nNamed controls joy 0'
        for control in joystick.device.get_controls():
            if isinstance(control, pyglet.input.base.Button):
                info = 'cls: Button'
            elif isinstance(control, pyglet.input.base.AbsoluteAxis):
                info = 'cls: Axis min:%s max:%s'%(control.min, control.max)
            else:
                info = 'cls: unknown'

            if control.name is None:
                if isinstance(control, pyglet.input.base.Button):
                    info = 'Button'
                    if control.name is None:
                        control.name = 'btn_%02d'%btn_cnt
                        btn_cnt += 1
                else:
                    control.name = 'ctrl_%02d'%ctrl_cnt            
            ctrl_cnt += 1
            if bPrint:
                print '\t(info:%s) %s'%(info,control.name)
    return joysticks

def make_axis_to_2buttons_converter(center_min, center_max,
                                    dest_low, low_button_cmd,
                                    dest_high, high_button_cmd):
    #works both for digital and analog axis
    def fn(buttons, value):
        # normal operation: value in [0.0  1.0] and
        # 0.0 <= center_min < center_max <= 1.0
        # buttons must be zeroed before the 'read-the-controls' loop to
        # allow alt binding. example: pyhs btn_00 and axis x low sets the 'left'
        # virtual button
        if value<center_min:
            buttons[dest_low][low_button_cmd] = 1
        elif value>center_max:
            buttons[dest_high][high_button_cmd] = 1
    return fn

def make_button_aquire(dest, cmd):
    def fn(buttons, control):
        if control.value:
            buttons[dest][cmd] = 1
    return fn

def make_axis_aquire(fn_reshape, fn_converter):
    """
    fn_reshape: a func [0.0  1.0] -> [0.0 1.0] Usually effectivizes
        'invert axis', sensivity and other customizations, like bezier
        controlled shape
    fn_converter: moves the phys reshaped value to the desired buttons value(s)
    """
    def fn(buttons, control):
        #normalize
        #print control.name # sometimes crash in next line; maybe we need
        # some time between open the joystick and using it
        value = control.value/float(control.max-control.min)
        # clamp
        if value < 0.0:
            value = 0.0
        elif value > 1.0:
            value = 1.0
        # reshape : usually effectivizes 'invert axis', sensivity and
        # other customizations, like bezier controlled shape
        value = fn_reshape(value)
        # aquire
        fn_converter(buttons, value)
    return fn

# reshape funcions expects values in [0.0 1.0] and returns values in [0.0 1.0]
def reshape_inverter(value):
    return 1.0-value

def reshape_unchange(value):
    return value
    
def make_bind_data_PS2base(dest):
    profile = 'PS2base'
    j_to_clear = []
    j_binds = {}
    #left, right
    fn_converter = make_axis_to_2buttons_converter(
        0.4, 0.6,
        dest, 'left',
        dest, 'right')
    j_to_clear.append((dest, 'left'))
    j_to_clear.append((dest, 'right'))
    j_binds['x'] = make_axis_aquire(reshape_unchange, fn_converter)
    #up, down
    fn_converter = make_axis_to_2buttons_converter(
        0.4, 0.6,
        dest, 'up',
        dest, 'down')
    j_to_clear.append((dest, 'up'))
    j_to_clear.append((dest, 'down'))
    j_binds['y'] = make_axis_aquire(reshape_unchange, fn_converter)

    j_binds['btn_00'] = make_button_aquire(dest,'quack_unfollow')
    j_to_clear.append((dest,'quack_unfollow'))
    # coop_sp 'orders' button
    j_binds['btn_01'] = make_button_aquire(dest,'coop_order')
    j_to_clear.append((dest,'btn_01'))
    return j_binds, j_to_clear
    



#### what follows is for debuging purposes
joystick = None
old = None
def track_joystick_activity(dt):
    #? very crude, for generic use needs autocalibration to ignore
    # noisy controls
    global joystick, old
    j_state = {}
    for control in joystick.device.get_controls():
        j_state[control.name] = control.value
    if old is not None:
        diff = {}
        for k in j_state:
            if j_state[k] != old[k]:
                diff[k] = j_state[k]
                if k != 'ctrl_02': # noisy ctrl, ignored to not spam console
                    print k, j_state[k]

    old = j_state

def test():
    import pyglet.gl as gl
    global joystick, old

    window = pyglet.window.Window()
    joysticks = enumerate_joysticks(bPrint=True)
    joystick = joysticks[0]
    joystick.open()

    @window.event
    def on_draw():
        x = (joystick.x + 1) * window.width / 2
        y = (-joystick.y + 1) * window.height / 2
        z = joystick.z
        angle = joystick.rz * 180

        # Axes

        gl.glClear(gl.GL_COLOR_BUFFER_BIT)
        gl.glColor3f(1, 0, 0)
        gl.glLoadIdentity()
        gl.glTranslatef(x, y, 0)
        gl.glScalef(1 + z, 1 + z, 1 + z)
        gl.glRotatef(-angle, 0, 0, 1)
        gl.glBegin(gl.GL_TRIANGLES)
        gl.glVertex2f(-10, 0)
        gl.glVertex2f(0, 13)
        gl.glVertex2f(10, 0)
        gl.glEnd()

        # Buttons

        gl.glLoadIdentity()
        x = 10
        y = 10
        gl.glPointSize(5)
        gl.glBegin(gl.GL_POINTS)
        for button in joystick.buttons:
            if button:
                gl.glVertex2f(x, y)
            x += 20
        gl.glEnd()

        # Hat

        gl.glColor3f(0, 0, 1)
        x = window.width / 2
        y = window.height / 2
        gl.glBegin(gl.GL_POINTS)
        gl.glVertex2f(x + joystick.hat_x * 50, y + joystick.hat_y * 50)
        gl.glEnd()

    pyglet.clock.schedule(track_joystick_activity)
    pyglet.app.run()

if __name__ == '__main__':
    test()

# sample test run in win XP with  a generic, USB, playstation style joystick
# (Sol Tech j-850S-D)
##Named controls joy 0
##        (info:cls: Axis min:0 max:65535) rz
##        (info:cls: Axis min:0 max:65535) z
##        (info:cls: Axis min:0 max:65535) ctrl_02
##        (info:cls: Axis min:0 max:65535) y
##        (info:cls: Axis min:0 max:65535) x
##        (info:cls: Axis min:0 max:4294967295) hat
##        (info:Button) btn_00
##        (info:Button) btn_01
##        (info:Button) btn_02
##        (info:Button) btn_03
##        (info:Button) btn_04
##        (info:Button) btn_05
##        (info:Button) btn_06
##        (info:Button) btn_07
##        (info:Button) btn_08
##        (info:Button) btn_09
##        (info:Button) btn_10
##        (info:Button) btn_11
## [ not copied the interactive part ]

##actual mapings seen in the same session:
##(ignoring ctl2_2 wich was spaming )
##with the mode light off:
##    works as a digital joystick, with 3 discrete values in each axis:
##    left button group and left analog stick -> axis x-y, values 0 : left resp up,
##    32511 center, 65535 right, resp down
##    right button group and analog stick right map to the same buttons (those
##    with the psp ident glyphs)
##
##with the mode light on:
##    works as an analog joystick, with all int values between min and max:
##    stick left maps to x-y, 0: left resp up, 32767 center, 65535 right resp down
##    stick right maps to z (horizontal) rz (vertical), with same values.
##    the left buttons group is identified as a 'hat' control, giving discrete
##    values for the clockwise angle begining at the 12hs position. The values
##    were 0, 4500, 9000, 13500, 18000, 22500, 27000, 31500 ( matching the
##    rosewind points N, NE, E, ... )
##    the right buttons group binds to buttons 0,1,2,3
##
##without regard for the light on/off mode, the other buttons map as
## vertical buttons as l_up 4, l_dn 6, r_up 5, r_dn 7
## the two central buttons:
##     select (square shape) btn_08, start(triangle shaped) btn_09 
